﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BeanboxSoftware.BeanProxy.Internals;
using BeanboxSoftware.BeanProxy;
using System.Reflection;
using System.Diagnostics;

namespace BeanProxyTest
{
  [TestClass]
  public class MethodDefinitionTest
  {
    public int Foo(int i) { return 1; }
    public int Foo<T>(int i) { return 2; }
    public int Foo<T>(T i) { return 3; }
    public int Foo(object obj) { return 4; }

    [TestMethod]
    public void CandidateTest()
    {
      var defAction = new MethodDefinition(typeof(Func<int, int>), "Foo");
      Assert.IsTrue(defAction.IsSpecific);
      Assert.AreEqual(0, defAction.Templates.Count);

      var methods = new List<MethodDefinition>();

      foreach (var mi in this.GetType().GetMethods().Where(m => m.Name == "Foo"))
      {
        var md = new MethodDefinition(mi);
        md.BindTo(this);
        methods.Add(md);

        var invoker = mi.IsGenericMethod ? mi.MakeGenericMethod(typeof(int)) : mi;

        int result = (int)invoker.Invoke(this, new object[] { 1 });

        if (result == 1)
        {
          Assert.IsTrue(md.IsSpecific);
          Assert.AreEqual(0, md.Templates.Count);
        }
        else if (result == 2)
        {
          Assert.IsFalse(md.IsSpecific);
          Assert.AreEqual(1, md.Templates.Count);
          Assert.IsTrue(md.Parameters[0].IsSpecific);
        }
        else if (result == 3)
        {
          Assert.IsFalse(md.IsSpecific);
          Assert.AreEqual(1, md.Templates.Count);
          Assert.IsFalse(md.Parameters[0].IsSpecific);
          Assert.AreEqual(0, md.Parameters[0].TemplatePosition);
        }
        else if (result == 4)
        {
          Assert.IsTrue(md.IsSpecific);
          Assert.AreEqual(0, md.Templates.Count);
          Assert.IsTrue(md.Parameters[0].IsSpecific);
          continue;
        }

      }

      var bestFit = methods.BestFit(new MethodDefinition(typeof(Func<int, int>), "Foo"));
      Assert.AreEqual(1, bestFit.MethodInfo.Invoke(this, new object[] { 1 }));

      bestFit = methods.BestFit(new MethodDefinition(typeof(Func<int, int>), "Foo"), typeof(int));
      Assert.AreEqual(2, bestFit.MethodInfo.Invoke(this, new object[] { 1 }));

      //3 cannot be reached with int
      bestFit = methods.BestFit(new MethodDefinition(typeof(Func<double, int>), "Foo"));
      Assert.AreEqual(3, bestFit.MethodInfo.Invoke(this, new object[] { 1.0 }));

      bestFit = methods.BestFit(new MethodDefinition(typeof(Func<object, int>), "Foo"));
      Assert.AreEqual(4, bestFit.MethodInfo.Invoke(this, new object[] { 1 }));

      //one that cannot fit
      Assert.IsNull(methods.BestFit(new MethodDefinition(typeof(Action), "Foo")));
    }

    public delegate int FooType1<T>(T t);
    public delegate int FooType2<T>(List<T> list);
    public delegate int FooType3<T>(int i);

    [TestMethod]
    public void NestedTypeReplaceTest()
    {
      var list = new List<int>();

      var proxy = ProxyManager.CreateProxy<Nested>(new CtorConfig(DefaultBehavior.Real));

      //first test with get
      MethodConfig mc = null;
      mc = ProxyManager.LookupMethod<Func<List<int>, int>>(proxy, "Foo").Access;
      Assert.AreEqual(2, mc.Invoke(list));

      mc = ProxyManager.GetFunc<int, int>(proxy.Foo).Access;
      Assert.AreEqual(1, mc.Invoke(0));

      mc = ProxyManager.GetFunc<List<int>, int>(proxy.Foo).Access;
      Assert.AreEqual(2, mc.Invoke(list));

      mc = ProxyManager.GetFunc<List<int>, int>(proxy.Foo<int>).Access;
      Assert.AreEqual(2, mc.Invoke(list));

      //now with lookup
      mc = ProxyManager.LookupMethod<Func<int, int>>(proxy, "Foo").Access;
      Assert.AreEqual(1, mc.Invoke(0));

      mc = ProxyManager.LookupMethod<Func<List<int>, int>>(proxy, "Foo", typeof(List<int>)).Access;
      Assert.AreEqual(1, mc.Invoke(list));

      mc = ProxyManager.LookupMethod<Func<List<int>, int>>(proxy, "Foo", typeof(int)).Access;
      Assert.AreEqual(2, mc.Invoke(list));
    }

    public class Nested
    {
      public virtual int Foo<T>(T t)
      {
        return 1;
      }

      public virtual int Foo<T>(List<T> list)
      {
        return 2;
      }

      public virtual int Foo<T>(int i)
      {
        return 3;
      }
    }
  }
}
